/* @flow */

import Dep from './dep'
import VNode from '../vdom/vnode'
import { arrayMethods } from './array'
import {
  def,
  warn,
  hasOwn,
  hasProto,
  isObject,
  isPlainObject,
  isPrimitive,
  isUndef,
  isValidArrayIndex,
  isServerRendering
} from '../util/index'

const arrayKeys = Object.getOwnPropertyNames(arrayMethods)

/**
 * 在某些情况下，我们可能希望禁用组件更新计算中的观察。
 */
export let shouldObserve: boolean = true

export function toggleObserving (value: boolean) {
  shouldObserve = value
}

/**
 * Observer 类会附加到每一个被侦测的 object 上。
 * 一旦被附加上，Observer 会将 object 的所有属性转换为 getter / setter 的形式
 * 来收集属性的依赖，并且当属性发生变化时会通知这些依赖
 */
export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that have this object as root $data

  constructor (value: any) {
    this.value = value
    // 存储被收集的依赖
    this.dep = new Dep()
    this.vmCount = 0
    // 在 value 上新增一个不可枚举的属性 __ob__，这个属性的值就是当前 Observer 的实例
    def(value, '__ob__', this)
    // 我们并不希望去污染全局的 Array 函数，所以在 Observer 当中进行拦截即可
    if (Array.isArray(value)) {
      // 判断浏览器是否支持 __proto__ 
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }

  /**
   * walk 会将每一个属性都转换为 getter / setter 的形式来侦测变化
   * 这个方法只有在数据类型为 Object 时被调用
   */
  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }

  /**
   * 循环 Array 中的每一项，执行 observe 函数来侦测变化
   */
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

// helpers

/**
 * 通过使用__proto__拦截原型链来增加目标对象或数组
 */
function protoAugment (target, src: Object) {
  /* eslint-disable no-proto */
  target.__proto__ = src
  /* eslint-enable no-proto */
}

/**
 * 直接将 arrayMethods 身上的这些方法设置到被侦测的数组上。
 */
/* istanbul ignore next */
function copyAugment (target: Object, src: Object, keys: Array<string>) {
  for (let i = 0, l = keys.length; i < l; i++) {
    const key = keys[i]
    def(target, key, src[key])
  }
}

/**
 * 尝试为 value 创建一个 Observer 实例
 * 如果创建成功，直接返回新创建的 Observer 实例
 * 如果 value 已经存在一个 Observer 实例，则直接返回它
 */
export function observe (value: any, asRootData: ?boolean): Observer | void {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

/**
 * 定义一个响应式对象，给对象动态添加 getter 和 setter
 */
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  // 存储被收集的依赖
  const dep = new Dep()

  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }

  // observe(val) 返回数组的 Observer 实例
  let childOb = !shallow && observe(val)
  // 核心方法
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    // 在 getter 中收集依赖
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        // 向 dep 当中添加
        dep.depend()
        if (childOb) {
          // 通过 childOb 的 dep 执行 depend 方法来收集依赖
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    // setter 中触发依赖
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      // #7981: for accessor properties without setter
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      // 当我们在组件中对响应的数据做了修改，就会触发 setter 的逻辑，最后调用 dep.notify() 方法
      dep.notify()
    }
  })
}

// 对于新添加的属性进行侦测
/**
 * 
 * @param {可能是数组或者是普通对象} target 
 * @param {数组的下标或者是对象的键值} key 
 * @param {添加的值} val 
 * @returns 
 */
export function set (target: Array<any> | Object, key: any, val: any): any {
  if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
  }
  /**
   * target 是一个数组并且 key 是一个有效的索引值
   */
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    // 如果传递的索引值大于数组的 length，设置 target 的 length 为 索引值
    target.length = Math.max(target.length, key)
    // 通过 splice 方法把 val 设置到 target 中指定位置
    // 由于我们之前做了处理，在使用 splice 方法的时候，会触发拦截器，将数据转换为响应式
    target.splice(key, 1, val)
    return val
  }
  // 如果参数中的 key 已经存在于 target 中，说明这个 key 已经被侦测变化了，直接修改数据即可
  // 数据发生变化后，会自动向依赖发送通知
  if (key in target && !(key in Object.prototype)) {
    target[key] = val
    return val
  }
  // 处理新增的属性

  // 获取 target 的 __ob__ 属性
  const ob = (target: any).__ob__
  // 如果 target 是 Vue.js 的实例，或者是根数据对象，报错
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid adding reactive properties to a Vue instance or its root $data ' +
      'at runtime - declare it upfront in the data option.'
    )
    return val
  }
  // target 不是响应式，直接通过 key 和 val 在 target 上设置即可
  if (!ob) {
    target[key] = val
    return val
  }
  // target 是响应式数据，在响应式数据上新增一个属性，这种情况下需要追踪这个新增属性的变化
  defineReactive(ob.value, key, val)
  // 向 target 的依赖触发变化通知
  ob.dep.notify()
  // 返回 val
  return val
}

/**
 * 删除属性，并在必要时触发更改。
 */
export function del (target: Array<any> | Object, key: any) {
  if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`)
  }
  // target 是一个数组并且 key 是一个有效的索引值
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    // 删除 key，因为使用了 splice 方法，数组拦截器会自动向依赖发送通知
    target.splice(key, 1)
    return
  }
  // 获取 target 的 Observer 实例
  const ob = (target: any).__ob__
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid deleting properties on a Vue instance or its root $data ' +
      '- just set it to null.'
    )
    return
  }
  // 如果删除的 key 不是 target 自身的属性，直接退出程序
  if (!hasOwn(target, key)) {
    return
  }
  delete target[key]
  // 如果 target 不是一个响应式数据，直接删除
  if (!ob) {
    return
  }
  // target 是响应式数据，需要发送通知
  ob.dep.notify()
}

/**
 * Collect dependencies on array elements when the array is touched, since
 * we cannot intercept array element access like property getters.
 */
function dependArray (value: Array<any>) {
  for (let e, i = 0, l = value.length; i < l; i++) {
    e = value[i]
    e && e.__ob__ && e.__ob__.dep.depend()
    if (Array.isArray(e)) {
      dependArray(e)
    }
  }
}
